var isReadonly = function (element) {
    return $(element).is('.readOnly');
};
var isUnique = function (element) {
    return $(element).is('.unique');
};

var initializeSortableList = function (element, valueAccessor) {
    $(element).data("sortList", valueAccessor()); //attach meta-data
    $(element).sortable({
        cursor: 'pointer',
        opacity: 0.60,
        placeholder: 'sortPlaceholder',
        receive: function (event, ui) {
            if (!isReadonly(this)) {
                var item = ui.item.data("sortItem");
                var newParent = ui.item.parent().data("sortList");
                // check if receiving list is unique and if item already exists, cancel the event
                if (isUnique(ui.item.parent())) {
                    var isAlreadyInList = false;
                    ko.utils.arrayForEach(newParent(), function (newParentItem) {
                        if (newParentItem.id() == item.id()) {
                            isAlreadyInList = true;
                        }
                    });
                    if (isAlreadyInList) {
                        $(ui.sender).sortable('cancel');
                        return;
                    }
                }

                var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
                if (position >= 0) {
                    newParent.splice(position, 0, item.convert ? item.convert() : item);
                    if (isReadonly(ui.sender)) {
                        $(ui.sender).sortable('cancel');
                    } else {
                        ui.item.remove();
                    }
                }
            } else {
                ui.item.remove();
            }
        },
        remove: function (event, ui) {
            var item = ui.item.data("sortItem");
            var originalParent = ui.item.data("parentList");

            if (!isReadonly(this)) {
                originalParent.remove(item);
            }
        },
        stop: function (event, ui) {

            var item = ui.item.data("sortItem");
            var originalParent = ui.item.data("parentList");
            var newParent = ui.item.parent().data("sortList");

            if (originalParent === newParent) {
                var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
                if (position >= 0) {
                    originalParent.remove(item);
                    newParent.splice(position, 0, item);
                    ui.item.remove();
                }
            }
        },
        connectWith: '.sortableContainer'
    });
};

//connect items with observableArrays
ko.bindingHandlers.sortableList = {
    update: initializeSortableList,
    init: initializeSortableList
};

//attach meta-data
ko.bindingHandlers.sortableItem = {
    init: function (element, valueAccessor) {
        var options = valueAccessor();
        $(element).data("sortItem", options.item);
        $(element).data("parentList", options.parentList);
    }
};

//control visibility, give element focus, and select the contents (in order)
ko.bindingHandlers.visibleAndSelect = {
    update: function (element, valueAccessor) {
        ko.bindingHandlers.visible.update(element, valueAccessor);
        if (valueAccessor()) {
            setTimeout(function () {
                $(element).focus().select();
            }, 0); //new tasks are not in DOM yet
        }
    }
};

//track an index on items in an observableArray
ko.observableArray.fn.indexed = function() {
   //whenever the array changes, make one loop to update the index on each
   this.subscribe(function(newValue) {
       if (newValue) {
           var item;
           for (var i = 0, j = newValue.length; i < j; i++) {
               item = newValue[i];
               if (!ko.isObservable(item.$index)) {
                  item.$index = ko.observable();
               }
               item.$index(i);      
           }
       }   
   }); 
    
   this.valueHasMutated(); 
   return this;
};